package kr.ac.hanyang.cs.gradPrj;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Calendar;

import android.app.Activity;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class GradVCActivity extends Activity {

	private boolean bRecordingBtnClicked								= false;
	private Button recordBtn											= null;
	private Thread voiceCommand											= null;

	/** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        recordBtn														= (Button)findViewById(R.id.btnRecord);

        recordBtn.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				if (bRecordingBtnClicked == false) {
					recordBtn.setText("Now recording...");
					bRecordingBtnClicked								= true;
					
					voiceCommand										= new Thread(new VoiceCommand());
					voiceCommand.setDaemon(true);
					voiceCommand.start();
				}
				else {
					recordBtn.setText("Tap to record");
					bRecordingBtnClicked								= false;
				}
			}
		});

    }


    public class VoiceCommand implements Runnable {

    	private final int RECORDER_AUDIO_ENCODING						= AudioFormat.ENCODING_PCM_16BIT;
    	private final int RECORDER_CHANNELS								= AudioFormat.CHANNEL_IN_MONO;
    	private final int WAVE_CHANNEL_MONO								= 1;
    	private final int HEADER_SIZE									= 0x2c;	//44
    	private final int RECORDER_BPP									= 16;
    	private final int RECORDER_SAMPLERATE							= 0x3E80;
																				/*
																				 * 0x1F40  //  8000 samples/sec 
																				 * 0x2B11  // 11025 samples/sec 
																				 * 0x3E80  // 16000 samples/sec 
																				 * 0x5622  // 22050 samples/sec 
																				 * 0x7D00  // 32000 samples/sec 
																				 * 0xAC44  // 44100 samples/sec 
																				 * 0xBB80  // 48000 samples/sec 
																				 */

    	private boolean bNowRecording									= false;

    	private AudioRecord oAudioRecord								= null;
    	private int iBufferSize											= -1;
    	private int iAudioSize											= -1;
    	private final String sTmpWaveFileName							= "sesameTemp";
    	private BufferedInputStream oInputBufferStream					= null;
    	private BufferedOutputStream oOutputBufferStream				= null;
    	private String sTimestamp										= "";


    	public String getTimestamp() {
    	    String sTimestamp											= "";
    		Calendar oCalendar											= Calendar.getInstance();

    		sTimestamp													= String.format("%04d%02d%02d_%02d%02d%02d_%04d",
																					oCalendar.get(Calendar.YEAR)
																					, (oCalendar.get(Calendar.MONTH) + 1)
																					, oCalendar.get(Calendar.DAY_OF_MONTH)
																					, oCalendar.get(Calendar.HOUR_OF_DAY)
																					, oCalendar.get(Calendar.MINUTE)
																					, oCalendar.get(Calendar.SECOND)
																					, oCalendar.get(Calendar.MILLISECOND));

    		Log.d("TIMESTAMP", sTimestamp);
    	    return sTimestamp;
    	}
    	

    	public void recordReady() {
    		bNowRecording												= false;
    		iBufferSize													= AudioRecord.getMinBufferSize(RECORDER_SAMPLERATE
    																							, RECORDER_CHANNELS
    																							, RECORDER_AUDIO_ENCODING); //4096 byte (or 4096*4 byte?)

    		oAudioRecord												= new AudioRecord(MediaRecorder.AudioSource.MIC
    																							, RECORDER_SAMPLERATE
    																							, RECORDER_CHANNELS
    																							, RECORDER_AUDIO_ENCODING
    																							, iBufferSize);

    		sTimestamp													= getTimestamp();
    	}


    	public void recordStart() {
    		recordReady();    		
    		if (oAudioRecord == null) {
    			Log.e("ERROR", "Should call recordReady() to initialize AudioRecord object.");
    			return ;
    		}

    		bNowRecording												= true;
    		oAudioRecord.startRecording();
    	}


    	public void recordStop() {
    		bNowRecording												= false;
    	}


    	public short[] recordToShort() {
    		short[] recordData											= new short[iBufferSize];
    		
    		try {
    			while (bRecordingBtnClicked) {
    				if (oAudioRecord.read(recordData, 0, iBufferSize) == AudioRecord.ERROR_INVALID_OPERATION) {
    					Log.e("ERROR", "INVALID read operation while reading");
    				}
    			}
    			recordStop();
    		}
    		catch(Exception e) {
    			e.printStackTrace();
    		}
    		
    		for (int i = 0;i < recordData.length;i++) {
    			Log.i("INFO", "Short data ["+ i +"]: "+ recordData[i]);
    		}
    		
    		return recordData;
    	}


    	public File recordToFile() {
    		byte[] recordData											= new byte[iBufferSize]; 
    		File oRawFile												= new File(Environment.getExternalStorageDirectory() +"/_sesame/"+ sTmpWaveFileName +"_"+ sTimestamp +".dat");

    		try {
    			// write record data to raw file 
    			oOutputBufferStream										= new BufferedOutputStream(new FileOutputStream(oRawFile));

    			while (bRecordingBtnClicked) {
    				if (oAudioRecord.read(recordData, 0, iBufferSize) != AudioRecord.ERROR_INVALID_OPERATION) {
    					oOutputBufferStream.write(recordData, 0, iBufferSize);
    				}
    			}
    			oOutputBufferStream.flush();
    			oOutputBufferStream.close();
    			recordStop();

    		}
    		catch (Exception e) {
    			e.printStackTrace();
    		}
    		
    		return oRawFile;
    	}

    	
    	private short[] convertToShort(File oRawFile) {
    		int iRawDataSize											= (int) oRawFile.length();
    		int iSampleCnt												= iRawDataSize / 2;
    		int iOffset													= 0;
    		int iReadCnt												= 0;
    		byte[] aRawInByte											= new byte[iRawDataSize];
    		short[] aRawInShort											= new short[iSampleCnt];
    		FileInputStream oRawFIS										= null;

    		if (oRawFile.length() > Integer.MAX_VALUE) {
    			Log.e("ERROR", "File size has been exceeded");
    		}

    		try {
    			oRawFIS													= new FileInputStream(oRawFile);

    			while (iOffset < aRawInByte.length
						&& (iReadCnt = oRawFIS.read(aRawInByte, iOffset, aRawInByte.length-iOffset)) >= 0) {
					iOffset												+= iReadCnt;
				}

				// convert from big endian to little endian in encoding by 16bits
    			/*
    			for (int i = 0;i < iSampleCnt;i++) {
					int MSB												= aRawInByte[2*i];
					int LSB												= aRawInByte[2*i + 1];
					aRawInShort[i]										= (short)(MSB << 8 | (255 & LSB));
				}
				*/
    			ByteBuffer.wrap(aRawInByte).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(aRawInShort);
    		}
    		catch (Exception e) {
    			e.printStackTrace();
    		}

    		return aRawInShort;
    	}

    	
    	private void convertToWav(short[] aVoiceDataInShort) {
    	    byte[] aVoiceDataInByte										= new byte[aVoiceDataInShort.length * 2];
    	    ByteBuffer.wrap(aVoiceDataInByte).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().put(aVoiceDataInShort);
    	    File aWavFile												= new File(Environment.getExternalStorageDirectory() +"/_sesame/"+ sTmpWaveFileName +"_"+ sTimestamp +"_Comp.wav");

    		try {
    			// TODO: update iAudioSize
    			//iAudioSize												= aVoiceDataInShort.length * 2;		// is this right?
    			
				oOutputBufferStream										= new BufferedOutputStream(new FileOutputStream(aWavFile));
				oOutputBufferStream.write(getWaveHeader());
				oOutputBufferStream.write(aVoiceDataInByte);
				oOutputBufferStream.flush();
				oOutputBufferStream.close();
				oInputBufferStream.close();
    		}
    		catch (Exception e) {
    			e.printStackTrace();
    		}

    	}


    	public void convertToWav(File oRawFile) {
    		byte[] recordBuffer											= new byte[iBufferSize];
    		File oWavFile												= new File(Environment.getExternalStorageDirectory() +"/_sesame/"+ sTmpWaveFileName +"_"+ sTimestamp +".wav");

    		try {
				// copy raw data to wav file
				iAudioSize												= (int) oRawFile.length();	// the size of tempFile in bytes

				oInputBufferStream										= new BufferedInputStream(new FileInputStream(oRawFile));
				oOutputBufferStream										= new BufferedOutputStream(new FileOutputStream(oWavFile));
				oOutputBufferStream.write(getWaveHeader());
				while (oInputBufferStream.read(recordBuffer) != -1) {
					oOutputBufferStream.write(recordBuffer);
				}
				oOutputBufferStream.flush();
				oOutputBufferStream.close();
				oInputBufferStream.close();
    		}
    		catch (Exception e) {
    			e.printStackTrace();
    		}

    	}
    	

    	private byte[] getWaveHeader() {
    		byte[] header												= new byte[HEADER_SIZE];
    		int iTotalSize												= iAudioSize + 40;
    		long byteRate												= RECORDER_BPP * RECORDER_SAMPLERATE * WAVE_CHANNEL_MONO / 8; // 16 * 44100 * 1 / 8 = 88200
    		header[0]													= 'R'; // RIFF/WAVE header
    		header[1]													= 'I';
    		header[2]													= 'F';
    		header[3]													= 'F';
    		header[4]													= (byte) (iTotalSize & 0xff);
    		header[5]													= (byte) ((iTotalSize >> 8) & 0xff);
    		header[6]													= (byte) ((iTotalSize >> 16) & 0xff);
    		header[7]													= (byte) ((iTotalSize >> 24) & 0xff);
    		header[8]													= 'W';
    		header[9]													= 'A';
    		header[10]													= 'V';
    		header[11]													= 'E';
    		header[12]													= 'f'; // 'fmt ' chunk
    		header[13]													= 'm';
    		header[14]													= 't';
    		header[15]													= ' ';
    		header[16]													= 16; // 4 bytes: size of 'fmt ' chunk
    		header[17]													= 0;
    		header[18]													= 0;
    		header[19]													= 0;
    		header[20]													= (byte) 1; // format = 1 (PCMπÊΩƒ)
    		header[21]													= 0;
    		header[22]													= WAVE_CHANNEL_MONO;
    		header[23]													= 0;
    		header[24]													= (byte) (RECORDER_SAMPLERATE & 0xff);
    		header[25]													= (byte) ((RECORDER_SAMPLERATE >> 8) & 0xff);
    		header[26]													= (byte) ((RECORDER_SAMPLERATE >> 16) & 0xff);
    		header[27]													= (byte) ((RECORDER_SAMPLERATE >> 24) & 0xff);
    		header[28]													= (byte) (byteRate & 0xff);
    		header[29]													= (byte) ((byteRate >> 8) & 0xff);
    		header[30]													= (byte) ((byteRate >> 16) & 0xff);
    		header[31]													= (byte) ((byteRate >> 24) & 0xff);
    		header[32]													= (byte) RECORDER_BPP * WAVE_CHANNEL_MONO / 8; // block align = 16 * 1 / 8													= 2
    		header[33]													= 0;
    		header[34]													= RECORDER_BPP; // bits per sample = 16
    		header[35]													= 0;
    		header[36]													= 'd';
    		header[37]													= 'a';
    		header[38]													= 't';
    		header[39]													= 'a';
    		header[40]													= (byte) (iAudioSize & 0xff);
    		header[41]													= (byte) ((iAudioSize >> 8) & 0xff);
    		header[42]													= (byte) ((iAudioSize >> 16) & 0xff);
    		header[43]													= (byte) ((iAudioSize >> 24) & 0xff);
    		return header;
    	}


    	@Override
    	public void run() {
    		File oRawFile												= null;
    		short[] aRawInShort											= null;
    		short[] aVoiceDataInShort									= null;

    		Log.i("INFO", "recordStart() - [");
    		recordStart();
    		Log.i("INFO", "] - recordStart()");

    		Log.i("INFO", "recordToFile() - [");
    		oRawFile													= recordToFile();
    		Log.i("INFO", "] - recordToFile()");

    		Log.i("INFO", "convertToWav() - [");
    		convertToWav(oRawFile);
    		Log.i("INFO", "] - convertToWav()");
 
    		Log.i("INFO", "convertToShort() - [");
    		aRawInShort													= convertToShort(oRawFile);
    		Log.i("INFO", "] - convertToShort()");
    		
    		Log.i("INFO", "detectEndPoint() - [");
    		EndPointDetection oEPD										= new EndPointDetection(aRawInShort, RECORDER_SAMPLERATE);
    		aVoiceDataInShort											= oEPD.detectEndPoint();
    		Log.i("INFO", "] - detectEndPoint()");

    		Log.i("SIZE", "ORG ShortArraySize: "+ aRawInShort.length);
    		Log.i("SIZE", "NEW ShortArraySize: "+ aVoiceDataInShort.length);
    		
    		convertToWav(aVoiceDataInShort);
    		
    		Log.i("INFO", "findCoefficient() - [");
    		LinearPredictiveCoding oLPC									= new LinearPredictiveCoding();
    		double[] aLPCInDouble										= oLPC.findCoefficient(aVoiceDataInShort, RECORDER_SAMPLERATE);
    		Log.i("INFO", "] - findCoefficient()");

    		for (int i = 0;i < aLPCInDouble.length;i++) {
    			Log.i("LPC", "Array["+ i +"] ::("+ aLPCInDouble[i] +")");
    		}
    	}
    }

}